import DebugExt from '../NRLib/base/DebugExt';
import { EVENT_KEY_PLAY_AUDIO, IEventAudio, TAudioType } from '../NRLib/ccExt/AudioExt';
import ZegoEvents from './ZegoEvents';

/** 音效加载状态 */
const enum AudioLoadT {
    idle = 0,
    loading,
    success,
    faild
}

const { ccclass, property, menu } = cc._decorator;

/** 场景BGM控制文件 */
@ccclass
@menu('Audio/GameAudioPlayer')
export default class GameAudioPlayer extends cc.Component {

    // 默认背景音乐
    @property(cc.AudioSource)
    private readonly bgm: cc.AudioSource = null;
    /** 获取当前BGM */
    private get curBGMClip(): cc.AudioClip {
        if (!!this.bgm && !!this.bgm.clip && this.bgm.clip.isValid)
            return this.bgm.clip;
        else
            return null;
    }

    // 是否加载背景音乐目录
    @property
    private readonly loadBGM: boolean = false;
    @property({
        tooltip: "背景音乐文件夹在 resources 目录下的相对位置",
        visible() {
            return this.loadBGM;
        },
    })
    private readonly bgmPath: string = '';

    // 加载音效目录
    @property({
        tooltip: "音效文件夹在 resources 目录下的相对位置"
    })
    private readonly audioPath: string = 'Audio';


    /** 是否由第三方接管音效控制 */
    private _zegoAudioCtl: boolean = false;


    protected onLoad(): void {
        if (!this.bgm) {
            DebugExt.warn("背景音乐控件为空！");
            this._bgmLoadStatus = AudioLoadT.faild;
        }

        // 監聽音效設置變化
        yy.event.on(this, yy.event.sys.SettingChanged, (e: yy.event.sys.SettingChanged) => {
            if (e.settingType == 1 || e.settingType == 2 || e.settingType == 6) {
                this.updateBGM();
            }
        });

        // 监听音效播放事件
        yy.event.on(this, yy.event.CustomEvent, this.onYYCustomEvent);
    }

    protected onDestroy(): void {
        yy.event.off(yy.event.CustomEvent, this.onYYCustomEvent);
        // 移除背景音效
        this.stopBGM();
        this.stopAllSubBgm();
    }

    /** 暂不支持音效停播 */
    private onYYCustomEvent(e: yy.event.CustomEvent) {
        if (e.eventID == EVENT_KEY_PLAY_AUDIO && !!e.param) {
            const data: IEventAudio = e.param as IEventAudio;
            if (!data) return;

            const audioName: string = data.audioName;
            const type: TAudioType | undefined = data.type;

            if (type == TAudioType.BGM && !!this.bgm) {
                // change BGM if it is exist
                if (this.curBGMClip.name != audioName && audioName.length > 0) {
                    const clip = this._bgmClips.get(audioName);
                    if (clip) {
                        if (this._zegoAudioCtl && this.curBGMClip) {
                            ZegoEvents.stopAudio(this.curBGMClip, yy.setting.musicVolume);
                        }
                        this.bgm.clip = clip;
                        this._bgmPrecent = 0;
                        this.updateBGM();
                    }
                    else
                        DebugExt.warn("背景音乐", audioName, "不存在！");
                }
            }
            else if (type == TAudioType.Effect) {
                this.playEffect(audioName);
            }
            else if (type == TAudioType.Voice) {
                this.playVoice(audioName);
            }
            else if (type == TAudioType.SubBgm) {
                const stop: boolean = !!data.stop;
                if (stop)
                    this.stopSbuBgm(audioName);
                else
                    this.playSubBgm(audioName);
            }
        }
    }

    protected start(): void {
        // bgm
        if (!this.loadBGM && !!this.curBGMClip) {
            this._bgmClips.set(this.curBGMClip.name, this.curBGMClip);
            this._bgmLoadStatus = AudioLoadT.success;
        }
        else if (this.loadBGM && this.bgmPath.length > 0) {
            this.loadClips('BGM', this.bgmPath);
        }
        else
            this._bgmLoadStatus = AudioLoadT.faild;

        if (this.audioPath.length > 0) {
            this.loadClips('Audio', this.audioPath);
        }
        else
            this._audioLoadStatus = AudioLoadT.faild;

        this.initZegoAC();
    }

    /** 初始化zego音效设置 */
    private initZegoAC() {
        if (!this.isBGMLoaded || !this.isAudioLoaded)
            return;

        this._zegoAudioCtl = ZegoEvents.initAudioConfig();

        if (!!this.bgm)
            this.bgm.enabled = !this._zegoAudioCtl;

        if (this._zegoAudioCtl) {
            let clips: yy.IAudioClip[] = [];
            this._bgmClips.forEach(clip => clips.push({ clip, loop: true, volume: yy.setting.musicVolume }));
            this._audioClips.forEach(clip => clips.push({ clip, loop: false, volume: yy.setting.effectVolume }));
            ZegoEvents.uploadClips(clips);
        }

        this.updateBGM();
    }

    /** 是否静音 */
    private get isMute(): boolean {
        return yy.setting.isMute || cc.game.isPaused();
    }
    /** 是否支持播放BGM */
    private get bgmOn(): boolean {
        return !this.isMute && yy.setting.isMusicEnable;
    }
    /** 是否支持播放音效--(当前游戏约定，不细分，采用静音选项判别) */
    private get effectOn(): boolean {
        return !this.isMute && yy.setting.isEffectEnable && yy.setting.effectVolume > 0;
    }
    /** 是否支持播放人物语音--(当前游戏约定，不细分，采用静音选项判别) */
    private get voiceOn(): boolean {
        return !this.isMute && yy.setting.isVoiceEnable && yy.setting.effectVolume > 0;
    }

    // 背景音乐启停
    private updateBGM() {
        if (this.bgmOn)
            this.playBGM();
        else
            this.stopBGM();
    }
    private playBGM(): void {
        if (!this.bgm || !this.bgm.clip || !this.bgmOn)
            return;
        if (this._zegoAudioCtl) {
            if (this._isBGMPlaying) {
                ZegoEvents.changeVolume(this.bgm.clip, yy.setting.musicVolume);
            }
            else {
                ZegoEvents.playAudio(this.bgm.clip, yy.setting.musicVolume);
                this._isBGMPlaying = true;
            }
        }
        else if (this.bgm.enabled) {
            if (this._isBGMPlaying)
                this._bgmPrecent = this.bgm.getCurrentTime();
            this.bgm.play();
            this.bgm.volume = yy.setting.musicVolume;
            this.bgm.setCurrentTime(this._bgmPrecent);
            this._isBGMPlaying = true;
        }
    }
    private stopBGM(): void {
        if (!this.bgm || !this.bgm.clip || !this._isBGMPlaying)
            return;
        if (this._zegoAudioCtl) {
            ZegoEvents.stopAudio(this.bgm.clip, yy.setting.musicVolume);
            this._isBGMPlaying = false;
        }
        else if (this.bgm.enabled) {
            this._bgmPrecent = this.bgm.getCurrentTime();
            this.bgm.stop();
            this._isBGMPlaying = false;
        }
    }
    private _bgmPrecent: number = 0;
    private _isBGMPlaying = false;

    /**
     * 播放指定音效
     * @param name 音效名
     */
    private playEffect(name: string) {
        if (!this.effectOn || !this._audioClips || !name) return;
        const clip: cc.AudioClip = this._audioClips.get(name);
        this.playClip(clip, name);
    }

    /**
     * 播放人物语音(判断语种)
     * @param name 音效名
     */
    private playVoice(name: string) {
        if (!this.voiceOn || !this._audioClips || !name) return;
        const defLang = name + '_en';
        const curLang = name + '_' + yy.curLanguage;
        const clip = this._audioClips.get(curLang) || this._audioClips.get(defLang);
        this.playClip(clip, name);
    }

    // 播放指定clip
    private playClip(clip: cc.AudioClip, name: string) {
        if (!clip) {
            DebugExt.warn("GameAudioPlayer 不存在音效:", name);
        }
        else {
            if (this._zegoAudioCtl)
                ZegoEvents.playAudio(clip, yy.setting.effectVolume);
            else {
                const id = cc.audioEngine.playEffect(clip, false);
                cc.audioEngine.setVolume(id, yy.setting.effectVolume);
            }
        }
    }


    /** 音效列表 */
    private _audioClips: Map<string, cc.AudioClip> = new Map();
    /** BGM列表 */
    private _bgmClips: Map<string, cc.AudioClip> = new Map();
    // 加载音效资源
    private loadClips(type: 'BGM' | 'Audio', path: string) {
        if (this.getClipsStatus(type) != AudioLoadT.idle) return;
        let map = type == 'BGM' ? this._bgmClips : this._audioClips;
        if (!map) return;
        this.setClipsStatus(type, AudioLoadT.loading);
        cc.loader.loadResDir(path, cc.AudioClip, (err, clips: cc.AudioClip[]) => {
            if (!err) {
                clips.forEach(clip => {
                    map.set(clip.name, clip);
                    DebugExt.log("加载音效资源成功, 路径：", path, clip.name);
                });
                this.setClipsStatus(type, AudioLoadT.success);
                if (type == 'BGM')
                    this.checkSubBgm();
            }
            else {
                DebugExt.warn("加载音效资源, 路径：", path, " 失败：", JSON.stringify(err));
                this.setClipsStatus(type, AudioLoadT.faild);
            }
            // end check
            this.initZegoAC();
        });
    }


    /** audio status */
    private get isAudioLoaded(): boolean {
        return this._audioLoadStatus == AudioLoadT.faild || this._audioLoadStatus == AudioLoadT.success;
    }
    private _audioLoadStatus: AudioLoadT = AudioLoadT.idle;
    /** BGM status */
    private get isBGMLoaded(): boolean {
        return this._bgmLoadStatus == AudioLoadT.faild || this._bgmLoadStatus == AudioLoadT.success;
    }
    private _bgmLoadStatus: AudioLoadT = AudioLoadT.idle;
    // 音效资源加载状态
    private getClipsStatus(type: 'BGM' | 'Audio'): AudioLoadT {
        return type == 'BGM' ? this._bgmLoadStatus : this._audioLoadStatus;
    }
    private setClipsStatus(type: 'BGM' | 'Audio', status: AudioLoadT) {
        if (type == 'BGM') {
            return this._bgmLoadStatus = status;
        }
        else {
            return this._audioLoadStatus = status;
        }
    }

    // 子背景音源列表
    private _subBgms: Map<string, number> = new Map();
    private stopAllSubBgm(): void {
        this._subBgms.forEach((id: number, name: string) => {
            if (id >= 0) this.stopSbuBgm(name);
        });
    }
    // 等待播放的子背景音效
    private _waitSubBgms: Map<string, boolean> = new Map();
    private checkSubBgm(): void {
        this._waitSubBgms.forEach((play: boolean, name: string) => {
            if (play) this.playSubBgm(name);
        });
    }
    /**
     * 播放指定背景音效
     * @param name 音效名
     */
    private playSubBgm(name: string) {
        DebugExt.log("##### playSubBgm:", name);
        if (!this.isBGMLoaded) {
            this._waitSubBgms.set(name, true);
            DebugExt.log("##### 添加待播音效", name);
            return;
        }
        if (!this.effectOn || !this._bgmClips || !name) return;
        const clip: cc.AudioClip = this._bgmClips.get(name);
        if (!clip) {
            DebugExt.warn("GameAudioPlayer 不存在音效:", name);
        }
        else {
            if (this._zegoAudioCtl) {
                ZegoEvents.playAudio(clip, yy.setting.effectVolume);
                this._subBgms.set(name, 0);
            }
            else {
                let id: number | undefined = this._subBgms.get(name);
                if (id == undefined || id < 0) {
                    const id = cc.audioEngine.playEffect(clip, true);
                    this._subBgms.set(name, id);
                }
                cc.audioEngine.setVolume(id, yy.setting.effectVolume);
            }
        }
    }
    /** 停止指定背景音效 */
    private stopSbuBgm(name: string) {
        DebugExt.log("##### stopSbuBgm:", name);
        if (!this.isBGMLoaded) {
            this._waitSubBgms.set(name, false);
            DebugExt.log("##### 移除待播音效", name);
            return;
        }
        if (!this._bgmClips || !name) return;
        if (this._zegoAudioCtl) {
            const clip: cc.AudioClip = this._bgmClips.get(name);
            if (!clip) {
                DebugExt.warn("GameAudioPlayer 不存在音效:", name);
            }
            else {
                ZegoEvents.stopAudio(clip, yy.setting.effectVolume);
                this._subBgms.set(name, -1);
            }
        }
        else {
            const id: number | undefined = this._subBgms.get(name);
            if (id != undefined && id >= 0) {
                cc.audioEngine.stopEffect(id);
                this._subBgms.set(name, -1);
            }
        }
    }
}
